/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package JET.mygui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JPanel;

import org.w3c.dom.*;


/**
 *
 * @author 057Ry
 */
public class MultiSplitPane extends JPanel implements ComponentListener, MouseListener, MouseMotionListener {
    
    protected ArrayList<MultiSplitPaneCell>   cells;
    protected int[]               sizes;
    protected boolean             horizontal;
    public final int              minCellSize = 40;
    
    // Resizing stuff
    private boolean             isResizingCells = false;
    private int                 lastMousePos, lastSizePrev, lastSizeNext;
    private int                 divIndex;

    private int                 space = 4;

    public MultiSplitPane(boolean horizontal) {
        setLayout(null);
        cells = new ArrayList<MultiSplitPaneCell>();
        sizes  = new int[0];

        this.horizontal = horizontal;

        // Listeners part
        addComponentListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);
    }
    public MultiSplitPane(int count, boolean horizontal) {
        setLayout(null);
        cells = new ArrayList<MultiSplitPaneCell>(count);
        for(int i=0; i<count; i++) {
            MultiSplitPaneCell cell = new MultiSplitPaneCell( true);
            cell.setLayout(new BorderLayout());
            //cell.addMouseMotionListener(new MultiSplitPanePanelsMouseMotionListener());

            cells.add(cell);
            add(cell);
        }

        // Setting panels sizes
        sizes  = new int[count];

        this.horizontal = horizontal;

        // Listeners part
        addComponentListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public Component add(Component comp) {
        Component parent = getParent();
        Color bgColor = parent==null? Color.WHITE : parent.getBackground();
        comp.setBackground(bgColor);
        super.add(comp);
        return comp;
    }

    public boolean isHorizontal() {
        return horizontal;
    }

    public MultiSplitPaneCell getCell(int index) {
        return cells.get(index);
    }

    public ArrayList<MultiSplitPaneCell> getCells() {
        return cells;
    }

    public int getCellIndex(MultiSplitPaneCell cell) {
        return cells.indexOf(cell);
    }

    public int getCellsCount() {
        return cells.size();
    }

    public int getCellSize(int index) {
        if (sizes != null && sizes.length>0)
            return sizes[index];

        return -1;
    }

    
    ////////////////////////////////////////////////////////////////////////////
    //        A D D I N G   &   R E M O V I N G   C O M P O N E N T S         //
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Adds components into specified cell of MultiSplitPane.
     * 
     * (Note: each cell can contain only one component, if you add component to
     *        cell that already contains some component, that component
     *        will be removed and probably forgotten ;) ).
     * 
     * @param comp Component to be added
     * @param index Index of cell to which component should be added
     *              (if multi split pane is horizontal indexes grow to the right,
     *               otherwise indexes grow downward)
     * @param repairSizes If true MSP tries to repair all cells sizes, if false
     *                    cell component is just inserted into cells components vector
     */
    public void setCellComponent(int index, JComponent comp, boolean repairSizes) {
        // Get rid of current component (if there is one)
        cells.get(index).removeAll();

        // Add new component
        cells.get(index).add(comp, BorderLayout.CENTER);
        cells.get(index).updateUI();
        if (repairSizes)
            repairSizes();
    }

    ////////////////////////////////////////////////////////////////////////////
    //           I N S E R T I N G   &   R E M O V I N G   C E L L S          //
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Inserts component into new created cell at specified location.
     *
     * @param comp Component to be inserted into multi split pane
     * @param index Position at which component should be inserted
     * @param size Initial size of cell comp is inserted into
     */
    public void insert(JComponent comp, boolean resizable, int index, int size) {

        // Limit index value to valid range
        if (index<0) index =0;
        else if (index>cells.size()) index = cells.size();

        // Copy table of sizes into new bigger table
        sizes = MyGuiHelper.insertValueAt(size, sizes, index);

        // Add new cell
        MultiSplitPaneCell newCell = new MultiSplitPaneCell(resizable);
        cells.add(index, newCell);
        add(newCell);

        // Fill cell with user component
        setCellComponent(index, comp, true);

        repairSizes();
        resetCellsBounds();
        updateUI();
    }

    public MultiSplitPaneCell removeCell(int index) {
        if (cells.size()>1) {

            MultiSplitPaneCell cell = cells.get(index);

            remove(cell);
            cells.remove(index);
            
            sizes = MyGuiHelper.removeValueAt(index, sizes);

            repairSizes();
            updateUI();
            
            return cell;
        }

        return null;
    }

    /**
     * Removes cell which has child that is specified comp.
     * @param comp Component indicating which MultiSplitPane cell should be removed.
     */
    public void removeCellWithComp(JComponent comp) {
        for(int i=0; i<cells.size(); i++)
            if (cells.get(i).containsComponent(comp))
                removeCell(i);
    }

    ////////////////////////////////////////////////////////////////////////////
    //        S I Z E   &   P O S I T I O N S   C A L C U L A T I O N S       //
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Calculates sum size of first n cells.
     * @param n number of cells that sizes we sum together (from left/top).
     * @return sum of n cells sizes from left/top
     */
    protected int getSumSizeOfNElems(int n) {
        int sum = 0;
        for(int i=0; i<n; i++) {
            sum += sizes[i];
        }
        return sum;
    }

    /**
     * Calculates size of multi split pane
     * @return size of multi split pane
     */
    protected int getAllowedSize() {
        return horizontal ?
                    getWidth() -  space*(cells.size()-1) :
                    getHeight() - space*(cells.size()-1);
    }

    /**
     * Calculates number of resizable cells in this MSP
     * @return number of resizable cells in this MSP
     */
    private int getResizableCellsCount() {
        int count = 0;
        for (MultiSplitPaneCell c : cells)
            if (c.isResizable()) count++;
        
        return count;
    }
    
    /**
     * Repairs cells sizes.
     */
    protected void repairSizes() {
        int rCount = getResizableCellsCount();

        // Scale all cells
        if (rCount==0 || rCount==cells.size()) {
            double extra = (getAllowedSize()-getSumSizeOfNElems(cells.size())) / (double)sizes.length;

            for(int i=0; i<sizes.length; i++)
                sizes[i] += extra;
        }
        
        // Scale resizable cells only
        else {
            int extraSum    = getAllowedSize()-getSumSizeOfNElems(cells.size());
            int extra       = (int)( (double)extraSum / (double)rCount );

            for(int c=0; c<cells.size(); c++)
                if (cells.get(c).isResizable()) {
//                    System.out.println("extending "+c+" cell ..., size: "+sizes[c]);
                    sizes[c] += extra;
//                    System.out.println("                      new size: "+sizes[c]);
                }
        }
    }

    /**
     * Sets size of specified cell
     * 
     * @param index Cell index in this multi split pane.
     * @param size new cell size
     */
    public void setCellSize(int index, int size) {
        sizes[index] = size;
    }
    /** Sets cells positions in MSP */
    protected void resetCellsBounds() {
        
        if (horizontal)
            for(int i=0; i<cells.size(); i++) {
                int  x = getSumSizeOfNElems(i) + space*i,
                     y = 0,
                     w = sizes[i],
                     h = getHeight();

//                System.out.println("MultiSplitPane.resetPanelsBounds(horizontal), x:"+x+" y:"+y+" w:"+w+" h:"+h);

                cells.get(i).setBounds(x, y, w, h);
            }
        else
            for(int i=0; i<cells.size(); i++) {
                int  x = 0,
                     y = getSumSizeOfNElems(i) + space*i,
                     w = getWidth(),
                     h = sizes[i];

//                System.out.println("MultiSplitPane.resetPanelsBounds(vertical), x:"+x+" y:"+y+" w:"+w+" h:"+h);

                cells.get(i).setBounds(x, y, w, h);
            }
    }

    /**
     * Returns index of divider at location value.
     * When split pane is horizontal value corresponds to x coordinate
     * when split pane is vertical value corresponds to y coordinate.

     * @param pos Location at which we look for divider.
     */
    private int getDivIndex(int pos) {
        int index = -1;

        for(int i=0; i<cells.size()-1; i++) {
            int limit1 = space*i + getSumSizeOfNElems(i+1),
                limit2 = space*(i+1) + getSumSizeOfNElems(i+1);
            
            if (pos>=limit1 && pos<=limit2) {
                index = i;
                break;
            }
        }

        return index;
    }

    /**
     * Tries to resize panels connecting with n'th divider, by value.
     *
     * (Note: this method can be called only when there is more than one panel in multi split pane,
     *        otherwise index out of bound exceptions will be thrown).
     * 
     * @param n Index of panels divider
     * @param value How much shift divider (lower than zero means left/up, higher thab zero means right/down)
     */
    private void tryToResize(int n, int value) {
        int newSizePrev = lastSizePrev+value,    // shift left/up
            newSizeNext = lastSizeNext-value;    // shift right/down

        if (newSizePrev<minCellSize) {
            newSizeNext -= minCellSize-newSizePrev;
            newSizePrev = minCellSize;
        } else if (newSizeNext<minCellSize) {
            newSizePrev -= minCellSize-newSizeNext;
            newSizeNext = minCellSize;
        }
        
        sizes[n]        = newSizePrev;
        sizes[n+1]      = newSizeNext;
    }

    public String toString(int level) {
        String str = "";
        for (int i=0; i<cells.size(); i++)
            str += MyGuiHelper.appendSpaces((level+1)*4, cells.get(i).toString(level+1) + "\n");
        String ret = "MSP[h:"+horizontal+"] {\n"+str+ MyGuiHelper.appendSpaces((level)*4,"}");
        MyGuiHelper.appendSpaces(level*4, ret);
        return ret;
    }

    /**
     * Cleans up mess when many nested msps of same orientation are enclosed in eachother ...
     *
     * @param rootContainer
     */
    public void cleanUpRedundance() {
        System.out.print("CleanUpRedundance() ... : ");
        for (int i=0; i<cells.size(); i++) {
            // If cell isn't empty
            if (cells.get(i).getComponentCount()>0) {
                Component c = MyGuiHelper.getComponentAfterScrollPane((JComponent) cells.get(i).getComponent(0));
                
                if (c instanceof MultiSplitPane) {
                    MultiSplitPane msp = (MultiSplitPane) c;

                    // If MSP has no cells
                    if (msp.getCellsCount()==0) {

                        System.out.println("0");

                        this.removeCellWithComp(msp);
                        this.cleanUpRedundance();
                        return;
                    }

                    // If MSP has single not empty cell
                    if (msp.getCellsCount() == 1 && msp.getCell(0).getComponentCount()>0) {

                        System.out.println("1");

                        Component compToShiftUp = msp.getCell(0).getComponent(0);
                        msp.getCell(0).remove(compToShiftUp);
                        cells.get(i).removeAll();
                        cells.get(i).add(compToShiftUp);
                        this.cleanUpRedundance();
                        return;
                    }

                    //If MSP has same orientation as this MSP
                    if (this.isHorizontal()==msp.isHorizontal()) {

                        System.out.println("2");

                        this.removeCell(i);
                        int insertIndex = i;
                        // Insert cells from msp to this, at proper position
                        for (int k=0; k<msp.getCellsCount(); k++) {
                            JComponent compToInsert = (JComponent) msp.getCell(k).getComponent(0);
                            insert(compToInsert, msp.getCell(k).isResizable(), insertIndex++, msp.getCellSize(k));
                        }
                        this.cleanUpRedundance();
                        return;
                    }

                    msp.cleanUpRedundance();
                }
            }
        }
    }

    public void setResizeingCursorIfNeeded(MouseEvent e) {
        int pos = horizontal ? e.getX() : e.getY();
        if (getDivIndex(pos) != -1)
            this.setCursor(horizontal ? new Cursor(Cursor.W_RESIZE_CURSOR) : new Cursor(Cursor.N_RESIZE_CURSOR));
        else
            this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }

    ////////////////////////////////////////////////////////////////////////////
    //                      M O U S E   L I S T E N E R                       //
    ////////////////////////////////////////////////////////////////////////////

    public void mouseClicked(MouseEvent e) {}
    public void mousePressed(MouseEvent e) {
//        System.out.println("MultiSplitPane.mousePressed(), evt:"+e);
//        System.out.println("MultiSplitPane.mousePressed(), divider index:"+getDivIndex(e.getX()));
        int pos = horizontal ? e.getX() : e.getY();
        if ( (divIndex=getDivIndex(pos)) != -1) {
            isResizingCells = true;
            lastMousePos = horizontal ? e.getX() : e.getY();
            lastSizePrev = horizontal ? cells.get(divIndex).getWidth() : cells.get(divIndex).getHeight();
            lastSizeNext = horizontal ? cells.get(divIndex+1).getWidth() : cells.get(divIndex+1).getHeight();
        }
    }
    public void mouseReleased(MouseEvent e) {
        isResizingCells = false;
    }
    public void mouseEntered(MouseEvent e) {
        setResizeingCursorIfNeeded(e);
    }
    public void mouseExited(MouseEvent e) {
        this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }

    ////////////////////////////////////////////////////////////////////////////
    //               M O U S E   M O T I O N   L I S T E N E R                //
    ////////////////////////////////////////////////////////////////////////////

    public void mouseDragged(MouseEvent e) {
        if (isResizingCells && divIndex!=-1) {
            int newPos = horizontal ? e.getX() : e.getY();
            tryToResize(divIndex, newPos - lastMousePos);
            resetCellsBounds();
            updateUI();
            setResizeingCursorIfNeeded(e);
        }
    }
    public void mouseMoved(MouseEvent e) {
        setResizeingCursorIfNeeded(e);
    }

    ////////////////////////////////////////////////////////////////////////////
    //                  C O M P O N E N T   L I S T E N E R                   //
    ////////////////////////////////////////////////////////////////////////////

    public void componentResized(ComponentEvent e) {
        repairSizes();
        resetCellsBounds();
        updateUI();
    }

    public void componentMoved(ComponentEvent e) {}
    public void componentShown(ComponentEvent e) {}
    public void componentHidden(ComponentEvent e) {}

    ////////////////////////////////////////////////////////////////////////////
    //                        X M L   S T U F F                               //
    ////////////////////////////////////////////////////////////////////////////

    public Element getXMLElement(Document doc) {
        Element root = doc.createElement("msp");

        root.setAttribute("width", ""+this.getWidth());
        root.setAttribute("height", ""+this.getWidth());
        root.setAttribute("cells", ""+this.cells.size());
        root.setAttribute("horizontal", ""+this.horizontal);

        try {
            for(MultiSplitPaneCell cell : cells)
                root.appendChild(cell.getXMLElement(doc));
        } catch(Exception e) {e.printStackTrace();}

        return root;
    }
}